(function () {
  var offsetY = 5;
  var offsetX = 10;
  var heightPart = 4;
  var speedBase = 30;
  var minHeight = 200;
  var lineHieght = 1.1;
  var defaultOptions = {
    width: window.innerWidth,
    height: null,
    fontSize: 15,
    coverage: 1,
    opacity: 1,
    speed: 2,
  }

  var utils = {
    random: function (min, max) {
      return Math.floor(Math.random() * (max - min) + min);
    },
    forEach: function (data, cb, context) {
      context = context || this;
      if (data) {
        if (data.constructor === Array) {
          for (var i = 0; i < data.length; i++) {
            cb.call(context, data[i], i);
          }
        } else {
          for (var key in data) {
            if (data.hasOwnProperty(key)) {
              var item = data[key];
              cb.call(context, item, key);
            }
          }
        }
      }
    },
    addClass: function (node, className) {
      node.className += ((node.className ? ' ' : '') + 'danmaku-' + className);
    },
    addStyles: function (node, styles) {
      for (var key in styles) {
        node.style[key] = styles[key];
      }
    }
  };

  var Danmaku = function (selector, options) {
    if (typeof selector === 'string') {
      this._wrapper = document.querySelector(selector);
    } else if (typeof selector === 'object') {
      this._wrapper = selector;
    } else {
      throw new Error('The first argument [selector] is required and must be a String or element object');
    }

    this.options = defaultOptions;

    for (var key in options) {
      this.options[key] = options[key];
    }

    this._init();
  }

  Danmaku.prototype = {
    _init: function () {
      this.timer = null;
      this.status = 0;
      this.pendingNodes = [];
      this.runningNodes = [];
      this.danmakuPond = [];
      this.userDanmakus = [];
      this.trajectory = [0, 0, 0];
      this._initEvent();
      this._insertDanmakuBox();
    },
    _initEvent: function () {},
    _insertDanmakuBox: function () {
      var box = this._box = document.createElement('div');
      utils.addStyles(this._wrapper, {
        position: 'relative'
      });
      utils.addClass(this._wrapper, 'wrapper');
      utils.addClass(box, 'box');
      this._wrapper.appendChild(box);
      if (this.options.height === null) {
        window.setTimeout(function (me) {
          me.options.height = Math.max(box.getBoundingClientRect().height, minHeight);
          me._calcTrajectory();
        }, 2000, this);
      } else {
        this._calcTrajectory();
      }
    },
    _calcTrajectory: function () {
      var fontSize = this.options.fontSize * 1.5;
      var height = this.options.height - fontSize;
      var coverage = this.options.coverage;
      var count = Math.ceil((height / heightPart * coverage) / (fontSize * lineHieght));
      this.trajectory.length = count;
    },
    _createDanmakuData: function (data) {
      var danmakus = [];
      var trajectory = this.trajectory;
      var width = this.options.width;
      var fontSize = this.options.fontSize * 1.5;
      var speed = this.options.speed;
      var busy = true;
      utils.forEach(data, function (item) {
        var i = 0;
        if (this.userDanmakus.length) {
          item = this.userDanmakus.shift();
        }
        item.status = 0;
        item.left = (width + offsetX) + 'px';
        item.width = item.text.length * fontSize;
        for (; i < trajectory.length; i++) {
          if (!trajectory[i]) {
            var trajectoryWidth = item.width + (fontSize);
            trajectory[i] = trajectoryWidth;
            item.top = (i * fontSize * lineHieght) + 'px';
            danmakus.push(item);
            busy = false;
            window.setTimeout(function () {
              if (trajectory[i]) trajectory[i] = 0;
            }, trajectoryWidth * speedBase / speed);
            break;
          }
        }
        if (busy === true && item._type === 1) {
          this.userDanmakus.unshift(item);
        }
      }, this);
      return danmakus;
    },
    _createDanmakuData2: function (data) {
      var danmakus = [];
      var trajectory = this.trajectory;
      var width = this.options.width;
      var fontSize = this.options.fontSize * 1.5;
      var speed = this.options.speed;
    },
    _createDanmakuNode: function (item) {
      var speed = this.options.speed;
      var width = this.options.width;
      var fontSize = this.options.fontSize;
      var opacity = this.options.opacity;
      var node = this.pendingNodes.shift();
      var duration = speedBase / speed;
      if (!node) {
        node = document.createElement('p');
        utils.addClass(node, 'item');
        this._box.appendChild(node);
      }
      this.runningNodes.push(node);
      node.innerText = item.text;
      utils.addStyles(node, {
        top: item.top,
        left: item.left,
        color: item.color || '#fff',
        fontSize: fontSize + 'px',
        opacity: opacity,
        border: 'none',
        webkitTransition: '-webkit-transform ' + duration + 's linear',
        webkitTransform: 'matrix(1, 0, 0, 1, 0, 0)'
      });
      if (item._type === 1) {
        utils.addStyles(node, {
          border: '1px solid ' + item.color
        });
      }
      node.dataset.startTime = Date.now();
      node.dataset.duration = duration;

      utils.addStyles(node, {
        webkitTransform: 'matrix(1, 0, 0, 1, -' + (width + item.width + offsetX) + ', 0)'
      });
      window.setTimeout(function (node) {}, 50, node);
    },
    _appendDanmakus: function () {
      this._recycleDanmuku();
      utils.forEach(this.danmakuPond, function (item) {
        if (item.status === 0) {
          this._createDanmakuNode(item);
          item.status = 1;
        }
      }, this);
    },
    _pauseDanmaku: function () {
      utils.forEach(this.runningNodes, function (node) {
        var left = window.parseInt(node.style.left) - node.getBoundingClientRect().left;
        var duration = node.dataset.duration;
        var time = (duration - (Date.now() - node.dataset.startTime) / 1000).toFixed(3);
        node.dataset.duration = time;
        node.dataset.transition = '-webkit-transform ' + Math.max(time, 0) + 's linear';
        node.dataset.transform = node.style.transform;
        utils.addStyles(node, {
          transition: '-webkit-transform 0s linear',
          transform: 'matrix(1, 0, 0, 1, -' + left + ', 0)'
        });
      }, this);
    },
    _recycleDanmuku: function () {
      utils.forEach(this.runningNodes, function (node, index) {
        var rect = node.getBoundingClientRect();
        if (rect.left < rect.width * -1) {
          node.style.transition = '';
          node.style.transform = '';
          this.runningNodes.splice(index, 1);
          this.pendingNodes.push(node);
        }
      }, this);
    },
    _playDanmaku: function () {
      utils.forEach(this.runningNodes, function (node) {
        node.dataset.startTime = Date.now();
        utils.addStyles(node, {
          transition: node.dataset.transition,
          transform: node.dataset.transform
        });
      }, this);
    },
    start: function () {
      if (this.status === 1 || this.status === 4) {
        return;
      }
      this.status = 1;
      this._playDanmaku();
    },
    push: function (data) {
      if (this.status !== 1 && this.status !== 4) {
        console.warn('Danmaku was paused or stopped');
        return;
      }
      var danmakus = this._createDanmakuData(data);
      this.danmakuPond = danmakus;
      this._appendDanmakus();
    },
    pause: function () {
      if (this.status === 2) {
        return;
      }
      this._pauseDanmaku();
      this.status = 2;
    },
    stop: function () {
      this.status = 3;
      this._box.innerHTML = '';
      this.runningNodes = [];
      this.pendingNodes = [];
    },
    send(item) {
      if (this.status === 0) {
        return;
      }
      item._type = 1;
      this.userDanmakus.push(item);
    },
    setOptions: function (data) {
      utils.forEach(data, function (val, key) {
        if (data.hasOwnProperty(key)) {
          if (key === 'fontSize' || key === 'speed') {
            this.status = 4;
            window.setTimeout(function (me) {
              me.status = 1
            }, 3000, this);
          }
          this.options[key] = val
        }
      }, this);
      this._calcTrajectory();
    }
  };

  window.Danmaku = Danmaku;
})()
